字符串相似度算法
1. 介绍
最近项目中有一个小算法要求判断字符串大致内容相等,相当于模糊查询,正好查到了这个字符串相似算法。这个算法又被称为“编辑距离算法”,所谓编辑距离,就是用来计算从原串(s)转换到目标串(t)所需要的最少的插入,删除和替换的数目。
2.算法原理
这里,我们举一个简单的例子,计算字符串“abc”“abe”的相似度。首先,需要构建一个二维数组,用来算最大编辑距离。如图:
初始化二维数组,从B2到E2和从B2到B5顺次为0,1,2…下面我们需要填写这个二维数组的每一格数据,最终得到我们从abc到abe所需的最大编辑距离。
这里,每一个格子是这样的
每一个格子由他的左边的格子上边的格子和左上角的格子决定。
具体来说,每一个方框的数字可以这样得来:
1. 左边的数字加一
2. 上边的数字加一
3. 如果这一格对应的行和列字母不同的话, 左上角的数字加一,否则加零。
举个例子,我们现在填如图选定的那个框(c3)。
根据上面的方法,这个框里的数字来源有三种 :
c2处得来的:1+1=2
b3处得来的:1+1=2
b2处得来的:因为a与a相同,所以0+0 = 0;
在这三个书中,我们找最小的数,即0填入即可。
看到这,恐怕很多看官很奇怪了,没事干为啥加一加零的,凭啥就左上角的数要分情况?不要急,我们在看一个例子,有了这个例子我想回更好理解。
这次我们填c4:
跟上边方法一样
c3框的0加一得到1
b2框的2加一得到3
这次因为a和b不同,因此左上角(b3)得到的数为:1+1=2
同样,取最小的数为1。
下面我将说一下我对表中每一个框算法的理解,可能并不准确,希望能有助于大家理解:
以计算c4为例,我们给c3+1,其实相当于在比较a和ab两个字符串,想让这两个字符串相等,唯一的方法就是加一个b字符,因此要加一。但是,有人可能要问了:
那为什么我们从b4算c4的时候是2+1呢,ab到a不应该是去掉一个b吗?那不应该是1吗?
这个问题是这样的,在这个算法中,无论字符串是什么,表中第一行和第一列(例如本例中1行和b列),他都是从0写到字符串长度,我是这样理解的,他这里是这样算的,我们将ab删掉需要两步,再添加一个a需要一步,这里不要将a和ab代入,你就想像他是yz和x,这样就可以理解了。
(这里你去观察,其实上面的加一和左面的加一是两个字符串互相变化最笨的方法,即删掉本身在加上对方,如果我们单单看从c2到c4,我们把它看成x到yz,其实应该为1+1+1也是3。这也是为什么左边和上边的算法不用考虑相不相同的原因,低头黑加就行了(手动滑稽),真正引起里面数字变化的,是左上角的那个判断,也正因为有左上角的参加,这里的由a3到a4变成了1)
再次声明,上面只是我的个人理解,并不完全保证正确。
好了下面你只需要把这个表填完:
然后我们就会发现:从abc到abe需要一步。
最后:先取两个字符串长度的最大值maxLen,用1-(需要操作数除maxLen),得到相似度。
例如本题,abc 和abe 需要一个操作,长度为3,所以相似度为1-1/3=0.666。
ok!大功告成。下面是Java代码,有兴趣的可以直接复制粘贴去运行一下!
3.代码实现
package code;
public class MyLevenshtein {
public static void main(String[] args) {
//要比较的两个字符串
String str1 = "今天星期四";
String str2 = "今天是星期五";
levenshtein(str1,str2);
}
public static void levenshtein(String str1,String str2) {
//计算两个字符串的长度。
int len1 = str1.length();
int len2 = str2.length();
//建立上面说的数组,比字符长度大一个空间
int[][] dif = new int[len1 + 1][len2 + 1];
//赋初值,步骤B。
for (int a = 0; a <= len1; a++) {
dif[a][0] = a;
}
for (int a = 0; a <= len2; a++) {
dif[0][a] = a;
}
//计算两个字符是否一样,计算左上的值
int temp;
for (int i = 1; i <= len1; i++) {
for (int j = 1; j <= len2; j++) {
if (str1.charAt(i - 1) == str2.charAt(j - 1)) {
temp = 0;
} else {
temp = 1;
}
//取三个值中最小的
dif[i][j] = min(dif[i - 1][j - 1] + temp, dif[i][j - 1] + 1,
dif[i - 1][j] + 1);
}
}
System.out.println("字符串\""+str1+"\"与\""+str2+"\"的比较");
//取数组右下角的值,同样不同位置代表不同字符串的比较
System.out.println("差异步骤:"+dif[len1][len2]);
//计算相似度
float similarity =1 - (float) dif[len1][len2] / Math.max(str1.length(), str2.length());
System.out.println("相似度:"+similarity);
}
//得到最小值
private static int min(int... is) {
int min = Integer.MAX_VALUE;
for (int i : is) {
if (min > i) {
min = i;
}
}
return min;
}
}